

namespace Plugins.Xml.Serialization
{
    using System.Collections.Generic;
    using System.IO;
    using System.Reflection;

    using Internal;

    using System;
    using System.Xml;
    using System.Xml.Linq;
    using System.Text;

    public class XmlObjectSerializer :
        IXmlObjectSerializer,
        IXmlRuntimeSerializer,
        IXmlObjectAttributeSerializer,
        IXmlObjectAttributeReader,
        IXmlObjectAttributeWriter,
        IXmlAttributeWriter,
        IXmlRuntimeReader,
        IXmlRuntimeWriter,
        IXmlRuntimePropertyReader,
        IXmlRuntimePropertyWriter,
        IXmlPropertyReader,
        IXmlPropertyWriter
    {
        private int _count;
        private int _id;
        private XElement _element;
        private Dictionary<int, XmlObjectSerializer> _nodes;
        private List<object> _names;

        string IXmlPropertyReader.Read(string propertyName)
        {
            int index = _names.IndexOf(propertyName);
            if (index >= 0)
            {
                return _nodes[index].Value;
            }
            return null;
        }
        string IXmlPropertyReader.Read(int index)
        {
            if (index >= 0 && index < _nodes.Count)
            {
                return _nodes[index].Value;
            }
            return null;
        }

        private int NewId()
        {
            return ++_count;
        }

        IXmlObjectAttributeReader IXmlAttributeReader.ReadAttributes { get { return this; } }
        IXmlObjectAttributeWriter IXmlAttributeWriter.WriteAttributes { get { return this; } }
        object IXmlObjectAttributeReader.this[string attributeName] { get { return Attributes[attributeName]; } }
        object IXmlObjectAttributeWriter.this[string attributeName] { set { Attributes[attributeName] = value; } }

        void IXmlPropertyWriter.Write(string propertyName, string value)
        {
            int index = _names.IndexOf(propertyName);
            if (index == -1)
            {
                AddPropertySerializer(propertyName);
                index = _nodes.Count - 1;
            }
            if (value != null)
            {
                _nodes[index].Value = value;
            }
        }
        void IXmlPropertyWriter.Write(int index, string value)
        {
            if (index >= 0 && index < _nodes.Count)
            {
                if (value != null)
                {
                    _nodes[index].Value = value;
                }
            }
        }

        private XmlObjectSerializer(int id, XElement element)
        {
            _id = id;
            _element = element;
            _nodes = new Dictionary<int, XmlObjectSerializer>();
            _names = new List<object>();
            _names.Add(_element.Name.ToString());
            _nodes.Add(0, this);
        }

        public int Id { get { return _id; } }
        public string Name { get { return _element.Name.ToString(); } }
        public IXmlObjectAttributeSerializer Attributes { get { return this; } }
        public IEnumerable<IXmlObjectSerializer> Elements
        {
            get
            {
                foreach (XmlObjectSerializer serializer in _nodes.Values)
                {
                    yield return serializer;
                }
            }
        }
        public string this[string propertyName]
        {
            get { return ((IXmlPropertyReader)this).Read(propertyName); }
            set { ((IXmlPropertyWriter)this).Write(propertyName, value); }
        }
        public string this[int index]
        {
            get { return ((IXmlPropertyReader)this).Read(index); }
            set { ((IXmlPropertyWriter)this).Write(index, value); }
        }

        private string Value { get { return _element.Value; } set { _element.SetValue(value); } }

        private XElement Element { get { return _element; } }

        private XmlObjectSerializer Load(XElement root)
        {
            XmlObjectSerializer node = AddPropertySerializer(root.Name.ToString());
            if (root.HasAttributes)
            {
                foreach (XAttribute attribute in root.Attributes())
                {
                    node.Load(attribute);
                }
            }
            if (root.HasElements)
            {
                foreach (XElement element in root.Elements())
                {
                    node.Load(element);
                }
            }
            if (root.NodeType == XmlNodeType.Element)
            {
                node.Value = root.Value;
            }
            return node;
        }

        private void Load(XAttribute root)
        {
            _element.SetAttributeValue(root.Name, root.Value);
        }

        private void AddObjectSerializer(XmlObjectSerializer node)
        {
            _element.Add(node.Element);
            _nodes.Add(node.Id, node);
            _names.Add(node.Name);
        }

        private void RemoveObjectSerializer(XmlObjectSerializer node)
        {
            node.Element.Remove();
            _nodes.Remove(node.Id);
            _names.Remove(node.Name);
        }

        private XmlObjectSerializer GetObjectSerializer(object obj, string name)
        {
            int index = _names.IndexOf(obj);
            if (index != -1)
            {
                XmlObjectSerializer node = _nodes[index];
                string nodeName = node.Name;
                if (nodeName == name)
                {
                    return node;
                }
            }
            return null;
        }

        public override string ToString()
        {
            return _element.Name.ToString();
        }

        void IXmlRuntimeSerializer.Deserialize(object obj)
        {
            if (_nodes.Count > 0)
            {
                foreach (XmlObjectSerializer serializer in _nodes.Values)
                {
                    ObjectProperty property = CreateProperty(obj, serializer.Name);
                    if (property != null)
                    {
                        if (property.SupportsRuntimeSerialization)
                        {
                            IXmlRuntimeReader reader = this;
                            reader.Read(property, serializer, property.Name);
                            continue;
                        }
                        if (property.SupportsSerialization)
                        {
                            IXmlRuntimePropertyReader reader = this;
                            IXmlPropertyReader propertyReader = this;
                            reader.Read(propertyReader, property, property.Name);
                            continue;
                        }
                    }
                }
            }
        }
        void IXmlRuntimeSerializer.Serialize(object obj)
        {
            foreach (ObjectProperty property in CreateProperties(obj))
            {
                if (property.SupportsRuntimeSerialization)
                {
                    IXmlRuntimeWriter writer = this;
                    writer.Write(property, AddPropertySerializer(property.Name), property.Name);
                    continue;
                }
                if (property.SupportsSerialization)
                {
                    IXmlRuntimePropertyWriter writer = this;
                    IXmlPropertyWriter propertyWriter = this;
                    writer.Write(propertyWriter, property, property.Name);
                    continue;
                }
            }
        }

        public static IXmlObjectSerializer Create(string name)
        {
            return new XmlObjectSerializer(0, new XElement(XName.Get(name)));
        }

        public static IXmlObjectSerializer Create(XElement root)
        {
            XmlObjectSerializer serializer = new XmlObjectSerializer(0, new XElement(XName.Get(root.Name.ToString())));
            foreach (XElement element in root.Elements())
            {
                serializer.Load(element);
            }
            return serializer;
        }

        private XmlObjectSerializer AddPropertySerializer(string name)
        {
            XmlObjectSerializer node = new XmlObjectSerializer(NewId(), new XElement(XName.Get(name)));
            AddObjectSerializer(node);
            return node;
        }

        private void Clear()
        {
            _element.RemoveAll();
            _nodes.Clear();
            _names.Clear();
        }

        string IXmlObjectSerializer.Deserialize()
        {
            return _element.ToString();
        }
        byte[] IXmlObjectSerializer.Serialize()
        {
            using (MemoryStream ms = new MemoryStream())
            {
                using (XmlTextWriter writer = new XmlTextWriter(ms, Encoding.UTF8))
                {
                    _element.Save(writer);
                }
                return ms.ToArray();
            }
        }
        object IXmlObjectAttributeSerializer.this[string attributeName]
        {
            get { return _element.Attribute(XName.Get(attributeName)).Value; }
            set { _element.SetAttributeValue(XName.Get(attributeName), value); }
        }
        IXmlObjectSerializer IXmlObjectSerializer.Add(object obj, string name)
        {
            XmlObjectSerializer node = new XmlObjectSerializer(NewId(), new XElement(XName.Get(name)));
            IXmlRuntimeSerializer serializer = node;
            serializer.Serialize(obj);
            AddObjectSerializer(node);
            return node;
        }
        IXmlObjectSerializer IXmlObjectSerializer.Remove(object obj, string name)
        {
            XmlObjectSerializer node = GetObjectSerializer(obj, name);
            IXmlRuntimeSerializer serializer = node;
            serializer.Deserialize(obj);
            RemoveObjectSerializer(node);
            return node;
        }

        #region IXmlReader Members

        void IXmlRuntimePropertyReader.Read(IXmlPropertyReader propertyReader, IObjectPropertyWriter property, string propertyName)
        {
            property.Write(propertyReader.Read(propertyName));
        }
        void IXmlRuntimePropertyReader.Read(IXmlPropertyReader propertyReader, IObjectPropertyWriter property, int index)
        {
            property.Write(propertyReader.Read(index));
        }
        void IXmlRuntimeReader.Read(IXmlReader reader, IXmlPropertyReader node, string propertyName)
        {
            reader.Read(node, propertyName);
        }
        void IXmlRuntimeReader.Read(IXmlReader reader, IXmlPropertyReader node, int index)
        {
            reader.Read(node, index);
        }

        #endregion

        #region IXmlWriter Members

        void IXmlRuntimePropertyWriter.Write(IXmlPropertyWriter propertyWriter, IObjectPropertyReader property, string propertyName)
        {
            propertyWriter.Write(propertyName, property.Read());
        }
        void IXmlRuntimePropertyWriter.Write(IXmlPropertyWriter propertyWriter, IObjectPropertyReader property, int index)
        {
            propertyWriter.Write(index, property.Read());
        }
        void IXmlRuntimeWriter.Write(IXmlWriter writer, IXmlPropertyWriter node, string propertyName)
        {
            writer.Write(node, propertyName);
        }
        void IXmlRuntimeWriter.Write(IXmlWriter writer, IXmlPropertyWriter node, int index)
        {
            writer.Write(node, index);
        }
        #endregion

        private static IEnumerable<ObjectProperty> CreateProperties(object obj)
        {
            Type type = obj.GetType();
            List<ObjectProperty> controlProperties = new List<ObjectProperty>();
            foreach (PropertyInfo property in RuntimePropertyHelper.GetInterfacePropertiesInternal(type))
            {
                ObjectProperty controlProperty = CreateProperty(obj, property.ReflectedType, property);
                if (controlProperty != null)
                {
                    controlProperties.Add(controlProperty);
                }
            }
            return controlProperties;
        }
        private static ObjectProperty CreateProperty(object obj, string propertyName)
        {
            Type type = obj.GetType();
            PropertyInfo property = RuntimePropertyHelper.GetInterfacePropertyInternal(type, propertyName);
            if (property != null)
            {
                return new ObjectProperty(obj, type, property);
            }
            return null;
        }
        private static ObjectProperty CreateProperty(object obj, Type type, PropertyInfo property)
        {
            if (property != null)
            {
                return new ObjectProperty(obj, type, property);
            }
            return null;
        }
    }
}